package plan;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class AlgoritmoA {

	private Estado inicial = new Estado();
	private Estado meta = new Estado();

	private List<Acoe> todasProposicoes = new ArrayList<Acoe>();

	String nomeSaida = "";
	

	public static void main(String[] args) {
		boolean isAdditive = true;
		String nomeArquivo = "Blocks-Ground/blocks-5-0.strips";
		//		String nomeArquivo = "Satellite-ground/satellite-2.strips";
		//		String nomeArquivo = "Blocks-Ground/blocks-7-1.strips";
		//		"Satellite-ground/satellite-9.strips"
		//String nomeArquivo = "Satellite-ground/satellite-2.strips";
		if((args.length != 2 && args.length != 4) || args[0].equals("--help")) {
			System.out.println("java -jar plan.jar -f <filename> (-add <true|false>)");
			System.exit(0);
		}
		else {
			nomeArquivo = args[1];
			if(args.length > 2)
				isAdditive = (args[3].equals("false")) ? false : true; 
		}


		StringBuffer saida = new StringBuffer();

		AlgoritmoA a = new AlgoritmoA();
		Long time = System.currentTimeMillis();
		Estado e;

		//ALGORITMO A* CON HEURISTICA SIMPLE
		
		if(args.length == 2)
			e = a.algoritmo(nomeArquivo, saida);
		else
			e = a.algoritmoHSPNovo(nomeArquivo, saida, isAdditive);

		//ALGORITMO A* CON HEURITSTICA HSP ADITIVA
		//		Estado e = a.algoritmoHSP(nomeArquivo, saida, true);

		//ALGORITMO A* CON HEURITSTICA HSP MAX
		//		Estado e = a.algoritmoHSP(nomeArquivo, saida, false);

		//ALGORITMO A* CON HEURITSTICA HSP NOVO
		

		time = System.currentTimeMillis() - time;
		System.out.println("RESULTADO ESTADO: ");
		saida.append("\nRESULTADO ESTADO: ");
		int numAcoes = 0;
		while(e.getPai() != null) {
			System.out.println("Acao: "+e.getAresta().getNome());
			System.out.println("Estado final: "+e.getProposicoes());
			saida.append("\nAcoe: " + e.getAresta().getNome());
			saida.append("\nProposicioes: " + e.getProposicoes());
			e = e.getPai();
			numAcoes++;
		}
		System.out.println("\nTempo Total: "+time.toString());
		System.out.println("\nNumero Total Acoes: "+numAcoes);
		saida.append("\nNumero Total Acoes: "+numAcoes);
		saida.append("\nTempo Total: "+time.toString());
//		a.gerarArchivo(saida.toString(),nomeArquivo+".result");
		a.gerarArchivo(saida.toString(),nomeArquivo+"."+a.nomeSaida+".result");		
	}


	public Estado algoritmo(String nomeArquivo, StringBuffer saida) {
		nomeSaida = "SIMPLE";
		List<Integer> proposicaoEstadoInicial = new ArrayList<Integer>();
		List<Integer> proposicaoEstadoMeta = new ArrayList<Integer>();
		List<Estado> listaAberta = new ArrayList<Estado>();
		List<Estado> listaFechada = new ArrayList<Estado>();
		Acoe a = new Acoe();
		this.todasProposicoes
		= a.iniciarAcoesStrip( nomeArquivo, 
				proposicaoEstadoInicial, proposicaoEstadoMeta);
		this.inicial.setProposicoes(proposicaoEstadoInicial);
		this.meta.setProposicoes(proposicaoEstadoMeta);

		this.inicial.setH(this.meta.getProposicoes().size());
		listaAberta.add(this.inicial);
		int tamamnhoListaAberta = listaAberta.size();
		int numEstadoVisitados = 0;
		while(tamamnhoListaAberta > 0) {
			Estado atual = encontraMaiorListaAberta(listaAberta,listaFechada);
			numEstadoVisitados ++;
//			saida.append("\nEstado visitado: "+numEstadoVisitados +" Altura: "+atual.getG()+" H: "+atual.getH()+" estado:"+atual.toString());
//			System.out.println("Estado visitado: "+numEstadoVisitados +" Altura: "+atual.getG()+" H: "+atual.getH()+" estado:"+atual.toString());
			// lista vazia
			if(atual == null)
				return null;
			List<Acoe> acoesAdjacentes = this.getAcoeAdjacentes(atual);
			for(Acoe acaoAdjacente : acoesAdjacentes) {
				Estado resultado = estadoFinal(acaoAdjacente, atual);
				if(estaNaLista(resultado, listaFechada) == null) {
					Estado elementoListaAberta = estaNaLista(atual, listaAberta);
					if(elementoListaAberta != null) {
						if(resultado.funcaoHeuristica() < elementoListaAberta.funcaoHeuristica()) {
							elementoListaAberta.setAresta(acaoAdjacente);
							elementoListaAberta.setPai(resultado.getPai());
							elementoListaAberta.setG(resultado.paiAltura()+1);
						}						
					}
					else {
						if(isSubconjuntoDeMeta(resultado, this.meta)) {
							System.out.println("\nNumero Estado visitados: "+numEstadoVisitados);
							return adicionaPaiECalculaGH(resultado, atual,acaoAdjacente);
							
						}
						listaAberta.add(adicionaPaiECalculaGH(resultado, atual,acaoAdjacente));
					}
				}
			}
			tamamnhoListaAberta = listaAberta.size();
		}
		return null;
	}

	public Estado algoritmoHSPNovo(String nomeArquivo, StringBuffer saida, boolean eAditivo) {
		//nome saida do arquivo
		nomeSaida = eAditivo?"ADD":"MAX";

		List<Integer> proposicaoEstadoInicial = new ArrayList<Integer>();
		List<Integer> proposicaoEstadoMeta = new ArrayList<Integer>();
		List<Estado> listaAberta = new ArrayList<Estado>();
		List<Estado> listaFechada = new ArrayList<Estado>();
		Acoe a = new Acoe();
		this.todasProposicoes
		= a.iniciarAcoesStrip(nomeArquivo, proposicaoEstadoInicial, proposicaoEstadoMeta);
		this.inicial.setProposicoes(proposicaoEstadoInicial);
		this.meta.setProposicoes(proposicaoEstadoMeta);

		//
		//
		listaAberta.add(this.inicial);
		int tamamnhoListaAberta = listaAberta.size();
		int numEstadoVisitados = 0;
		while(tamamnhoListaAberta > 0) {
			Estado atual = encontraMenorCusto(listaAberta,listaFechada);
			atual.setCusto((eAditivo) ? h_add(atual) : h_max(atual));
			numEstadoVisitados ++;
			//			saida.append("\nEstado visitado: "+numEstadoVisitados +" Altura: "+atual.getG()+" H: "+atual.getH()+" estado:"+atual.toString());
//			System.out.println("Estado visitado: "+numEstadoVisitados +" Altura: "+atual.getG()+" H: "+atual.getH()+" estado:"+atual.toString());
			// lista vazia
			if(atual == null)
				return null;
			List<Acoe> acoesAdjacentes = this.getAcoeAdjacentes(atual);

			for(Acoe acaoAdjacente : acoesAdjacentes) {
				Estado resultado = estadoFinal(acaoAdjacente, atual);
				if(estaNaLista(resultado, listaFechada) == null) {
					Estado elementoListaAberta = estaNaLista(atual, listaAberta);
					if(elementoListaAberta != null) {
						if(resultado.getCusto() < elementoListaAberta.getCusto()) {
							elementoListaAberta.setAresta(acaoAdjacente);
							elementoListaAberta.setPai(resultado.getPai());
							elementoListaAberta.setG(resultado.paiAltura()+1);
						}						
					}
					else {
						if(isSubconjuntoDeMeta(resultado, this.meta)) {
							System.out.println("\nNumero Estado visitados: "+numEstadoVisitados);
							return adicionaPaiECalculaGH(resultado, atual,acaoAdjacente);
						}
						listaAberta.add(adicionaPaiECalculaGH(resultado, atual,acaoAdjacente));
					}
				}
			}
			tamamnhoListaAberta = listaAberta.size();
		}
		return null;
	}


	public int  deltaZero(Estado atual, List<Acoe> acoesAdjacentes, 
			Integer p, int costoP){
		//List<Acoe> acoesAdjacentes = getAcoeAdjacentes(e);
		for(Acoe a:acoesAdjacentes) {
			if(a.getEfeitosPos().contains(p)) {
				return  1 + costoP;
			}
		}		
		int  delta = 0;
		for(Acoe a:acoesAdjacentes) {
			Estado e = estadoFinal(a, atual);
			List<Integer> proposicaoDeS = new ArrayList<Integer>(e.getProposicoes());
			List<Acoe> adjacentes = new ArrayList<Acoe>();
			if(proposicaoDeS.containsAll(a.getPreconPos()) == true) {
				// pre cond -(a) interseccao S = nulo(vazio)
				proposicaoDeS.retainAll(a.getPreconNeg());
				if(proposicaoDeS.size() == 0)
					adjacentes.add(a);
			}			
			delta = deltaZero(e, adjacentes, p, 1 + costoP);
		}
		return delta;
	}

	public int deltaZero2(List<Integer> s, List<Acoe> acoesAdjacentes, 
			Integer p){
		//List<Acoe> acoesAdjacentes = getAcoeAdjacentes(e);
		if(s.contains(p))
			return 0;
		boolean efeitoPositivo = false;
		for(Acoe a : acoesAdjacentes) {
			if(a.getEfeitosPos().contains(p)) {
				efeitoPositivo = true;
				break;
			}
		}
		if(efeitoPositivo == false)
			return Integer.MAX_VALUE;
		int[] auxMin = null;
		for(Acoe a : acoesAdjacentes)
			// p pertence efeitos +(a)
			if(a.getEfeitosPos().contains(p)) {
				auxMin = new int[a.getPreconPos().size()];
				int soma = 0;
				int i = 0;
				for(Integer q : a.getPreconPos())
					auxMin[i++] = 1 + deltaZero2(s, acoesAdjacentes, q);				

			}
		Arrays.sort(auxMin);
		return auxMin[0];
	}

	private int h_add(Estado s) {
		int soma = 0;
		for(Integer p : this.meta.getProposicoes())
			soma += deltaZero2(s.getProposicoes(), this.getAcoeAdjacentes(s), p);
		return soma;
	}

	private int h_max(Estado s) {
		int max = 0;
		for(Integer p : this.meta.getProposicoes()) {
			int temp = deltaZero2(s.getProposicoes(), this.getAcoeAdjacentes(s), p);
			max = (temp > max) ? temp : max;
		}
		return max;
	}


	List<Acoe> getAcoeAdjacentes(Estado s) {
		List<Acoe> adjacentes = new ArrayList<Acoe>();
		for(Acoe a : todasProposicoes) {
			// proposicoes do Estado atual
			List<Integer> proposicaoDeS = new ArrayList<Integer>(s.getProposicoes());
			// list has changed
			if(proposicaoDeS.containsAll(a.getPreconPos()) == true) {
				// pre cond -(a) interseccao S = nulo(vazio)
				proposicaoDeS.retainAll(a.getPreconNeg());
				if(proposicaoDeS.size() == 0)
					adjacentes.add(a);
			}
			//tamben precondiciones negativas nao esten no conjunto S

		}
		return adjacentes;
	}


	private boolean isSubconjuntoDeMeta(Estado resultado, Estado meta) {
		return resultado.getProposicoes().containsAll(meta.getProposicoes());
	}

	private Estado encontraMaiorListaAberta(List<Estado> listaAberta, List<Estado> listaFechada) {
		if(listaAberta.size() > 0) {
			Estado maior = listaAberta.get(0);
			for(Estado e : listaAberta) {
				if(maior.funcaoHeuristica() < e.funcaoHeuristica())
					maior = e;
			}
			listaFechada.add(maior);
			listaAberta.remove(maior);
			return maior;
		}
		return null;
	}

	private Estado encontraMenorCusto(List<Estado> listaAberta, List<Estado> listaFechada) {
		if(listaAberta.size() > 0) {
			Estado menor = listaAberta.get(0);
			for(Estado e : listaAberta) {
				if(menor.getCusto() > e.getCusto())
					menor = e;
			}
			listaFechada.add(menor);
			listaAberta.remove(menor);
			return menor;
		}
		return null;
	}

	private Estado adicionaPaiECalculaGH(Estado novo, Estado pai,Acoe acao) {
		novo.setAresta(acao);
		novo.setPai(pai);
		novo.setG(pai.getG()+1);
		novo.setH(calculaH(novo));
		return novo;
	}

	private int calculaH(Estado e) {		
		double h = 0;
		for(Integer proposicaoEstado : e.getProposicoes()) {
			for(Integer proposicoesMeta : this.meta.getProposicoes()) {
				if(proposicaoEstado.equals(proposicoesMeta))
					h += Math.pow(10, this.meta.getProposicoes().indexOf(proposicoesMeta));
			}
		}		
		return  (int)h;
	}

	private Estado estaNaLista(Estado e, List<Estado> lista) {
		for(Estado estado:lista) {
			if(saoIguais(e, estado))
				return estado;
		}
		return null;
	}

	public Estado estadoFinal(Acoe acao, Estado atual) {
		Estado estado = new Estado();
		List<Integer> proposicoes = new ArrayList<Integer>(atual.getProposicoes());
		proposicoes.removeAll(acao.getEfeitosNeg());
		proposicoes.addAll(acao.getEfeitosPos());
		estado.setProposicoes(proposicoes);
		return estado;
	}

	public boolean saoIguais(Estado a, Estado b) {
		List<Integer> compara;
		compara = new ArrayList<Integer>(a.getProposicoes());
		if(compara.retainAll(b.getProposicoes()) == true)
			return false;
		return true;
	}

	public void gerarArchivo(String contenido, String nombre){
		try{			
			FileWriter fstream = new FileWriter(nombre);			 
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(contenido);
			out.close();
		} catch(Exception e){
			e.printStackTrace();
		}
	}
}
